package com.zhouheng.common.jwtfilter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhouheng.common.constant.Constants;
import com.zhouheng.common.exception.BaseException;
import com.zhouheng.common.security.GrantedAuthorityImpl;
import com.zhouheng.module.dto.SysUserLoginDTO;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * jwt登录拦截器
 * <p>
 * 登录controller方法不用自己写，直接访问/login就行
 * 该类继承自UsernamePasswordAuthenticationFilter，重写了其中的2个方法
 * <p>
 * 验证用户名密码正确后，生成一个token，并将token返回给客户端
 * attemptAuthentication ：接收并解析用户凭证。
 * successfulAuthentication ：用户成功登录后，这个方法会被调用，我们在这个方法里生成token。
 *
 * @author 周恒
 * @date 20180831 16:17:09
 * @since v1.0
 */
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /**
     * 尝试身份验证
     * 处理登录请求，校验用户名和密码
     *
     * @param req 请求
     * @param res 响应
     * @author 周恒
     * @date 20200120 16:46:37
     * @since v1.0
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        System.out.println("JWTLoginFilter.attemptAuthentication");
        try {
            //请求头反序列化获得User对象
            SysUserLoginDTO sysUserLoginDTO = new ObjectMapper().readValue(req.getInputStream(), SysUserLoginDTO.class);
            //获取验证码，并对验证码进行校验
            String captcha = sysUserLoginDTO.getCaptcha();
            String sessionCaptcha = (String) req.getSession().getAttribute(Constants.SESSION_CAPTCHA);
            if (!sessionCaptcha.equalsIgnoreCase(captcha)) {
                throw new BadCredentialsException("验证码错误");
            }
            //登录时authorities现在是空的，登录校验成功后，会把权限写入token返回给前端，
            //前端访问接口时会带上token，权限校验时会解析token得到具体的权限
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            sysUserLoginDTO.getUsername(),
                            sysUserLoginDTO.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new BaseException(e);
        }
    }

    /**
     * 登录请求后，CustomAuthenticationProvider.authenticate身份验证通过会生成Authentication令牌
     * 我们在这个方法里对令牌生成token，并设置在响应头中
     *
     * @param req   请求
     * @param res   响应
     * @param chain 过滤链
     * @param auth  身份认证
     * @author 周恒
     * @date 20200120 16:46:37
     * @since v1.0
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        System.out.println("JWTLoginFilter.successfulAuthentication");
        List authorities = (List) auth.getAuthorities();
        int length = authorities == null ? 0 : authorities.size();
        //获取权限主题
        String subject = auth.getName();
        for (int i = 0; i < length; i++) {
            if (authorities.get(i) instanceof GrantedAuthorityImpl) {
                subject += "," + ((GrantedAuthorityImpl) authorities.get(i)).getAuthority();
            }
        }
        //subject中存入用户名和角色权限
        String token = Jwts.builder()
                //设置主题
                .setSubject(subject)
                //设置到期时间
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 24 * 1000))
                //选择 加密算法和私钥
                .signWith(SignatureAlgorithm.HS512, Constants.SIGNING_KEY)
                .compact();
        //token返回到请求头中，前端在请求头中获取
        res.addHeader(Constants.HEADER_AUTH, Constants.AUTH_HEADER_START_WITH + token);
    }

}
